View Javadoc
1   package org.apache.maven.surefire.junitcore.pc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.surefire.testset.TestSetFailedException;
23  import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
24  import org.junit.runner.Computer;
25  import org.junit.runner.Description;
26  
27  import java.util.Collection;
28  import java.util.TreeSet;
29  import java.util.concurrent.Callable;
30  import java.util.concurrent.ExecutionException;
31  import java.util.concurrent.Executors;
32  import java.util.concurrent.Future;
33  import java.util.concurrent.ScheduledExecutorService;
34  import java.util.concurrent.ThreadFactory;
35  
36  import static java.util.concurrent.TimeUnit.NANOSECONDS;
37  
38  /**
39   * ParallelComputer extends JUnit {@link Computer} and has a shutdown functionality.
40   *
41   * @author Tibor Digana (tibor17)
42   * @see ParallelComputerBuilder
43   * @since 2.16
44   */
45  public abstract class ParallelComputer
46      extends Computer
47  {
48      private static final ThreadFactory DAEMON_THREAD_FACTORY = DaemonThreadFactory.newDaemonThreadFactory();
49  
50      private static final double NANOS_IN_A_SECOND = 1E9;
51  
52      private final ShutdownStatus shutdownStatus = new ShutdownStatus();
53  
54      private final ShutdownStatus forcedShutdownStatus = new ShutdownStatus();
55  
56      private final long timeoutNanos;
57  
58      private final long timeoutForcedNanos;
59  
60      private ScheduledExecutorService shutdownScheduler;
61  
62      public ParallelComputer( double timeoutInSeconds, double timeoutForcedInSeconds )
63      {
64          this.timeoutNanos = secondsToNanos( timeoutInSeconds );
65          this.timeoutForcedNanos = secondsToNanos( timeoutForcedInSeconds );
66      }
67  
68      protected abstract ShutdownResult describeStopped( boolean shutdownNow );
69  
70      abstract boolean shutdownThreadPoolsAwaitingKilled();
71  
72      protected final void beforeRunQuietly()
73      {
74          shutdownStatus.setDescriptionsBeforeShutdown( hasTimeout() ? scheduleShutdown() : null );
75          forcedShutdownStatus.setDescriptionsBeforeShutdown( hasTimeoutForced() ? scheduleForcedShutdown() : null );
76      }
77  
78      protected final boolean afterRunQuietly()
79      {
80          shutdownStatus.tryFinish();
81          forcedShutdownStatus.tryFinish();
82          boolean notInterrupted = true;
83          if ( shutdownScheduler != null )
84          {
85              shutdownScheduler.shutdownNow();
86              /**
87               * Clear <i>interrupted status</i> of the (main) Thread.
88               * Could be previously interrupted by {@link InvokerStrategy} after triggering immediate shutdown.
89               */
90              Thread.interrupted();
91              try
92              {
93                  shutdownScheduler.awaitTermination( Long.MAX_VALUE, NANOSECONDS );
94              }
95              catch ( InterruptedException e )
96              {
97                  notInterrupted = false;
98              }
99          }
100         notInterrupted &= shutdownThreadPoolsAwaitingKilled();
101         return notInterrupted;
102     }
103 
104     public String describeElapsedTimeout()
105         throws TestSetFailedException
106     {
107         final StringBuilder msg = new StringBuilder();
108         final boolean isShutdownTimeout = shutdownStatus.isTimeoutElapsed();
109         final boolean isForcedShutdownTimeout = forcedShutdownStatus.isTimeoutElapsed();
110         if ( isShutdownTimeout || isForcedShutdownTimeout )
111         {
112             msg.append( "The test run has finished abruptly after timeout of " );
113             msg.append( nanosToSeconds( minTimeout( timeoutNanos, timeoutForcedNanos ) ) );
114             msg.append( " seconds.\n" );
115 
116             try
117             {
118                 final TreeSet<String> executedTests = new TreeSet<String>();
119                 final TreeSet<String> incompleteTests = new TreeSet<String>();
120 
121                 if ( isShutdownTimeout )
122                 {
123                     printShutdownHook( executedTests, incompleteTests, shutdownStatus.getDescriptionsBeforeShutdown() );
124                 }
125 
126                 if ( isForcedShutdownTimeout )
127                 {
128                     printShutdownHook( executedTests, incompleteTests,
129                                        forcedShutdownStatus.getDescriptionsBeforeShutdown() );
130                 }
131 
132                 if ( !executedTests.isEmpty() )
133                 {
134                     msg.append( "These tests were executed in prior to the shutdown operation:\n" );
135                     for ( String executedTest : executedTests )
136                     {
137                         msg.append( executedTest ).append( '\n' );
138                     }
139                 }
140 
141                 if ( !incompleteTests.isEmpty() )
142                 {
143                     msg.append( "These tests are incomplete:\n" );
144                     for ( String incompleteTest : incompleteTests )
145                     {
146                         msg.append( incompleteTest ).append( '\n' );
147                     }
148                 }
149             }
150             catch ( InterruptedException e )
151             {
152                 throw new TestSetFailedException( "Timed termination was interrupted.", e );
153             }
154             catch ( ExecutionException e )
155             {
156                 throw new TestSetFailedException( e.getLocalizedMessage(), e.getCause() );
157             }
158         }
159         return msg.toString();
160     }
161 
162     private Future<ShutdownResult> scheduleShutdown()
163     {
164         return getShutdownScheduler().schedule( createShutdownTask(), timeoutNanos, NANOSECONDS );
165     }
166 
167     private Future<ShutdownResult> scheduleForcedShutdown()
168     {
169         return getShutdownScheduler().schedule( createForcedShutdownTask(), timeoutForcedNanos, NANOSECONDS );
170     }
171 
172     private ScheduledExecutorService getShutdownScheduler()
173     {
174         if ( shutdownScheduler == null )
175         {
176             shutdownScheduler = Executors.newScheduledThreadPool( 2, DAEMON_THREAD_FACTORY );
177         }
178         return shutdownScheduler;
179     }
180 
181     private Callable<ShutdownResult> createShutdownTask()
182     {
183         return new Callable<ShutdownResult>()
184         {
185             public ShutdownResult call()
186                 throws Exception
187             {
188                 boolean stampedStatusWithTimeout = ParallelComputer.this.shutdownStatus.tryTimeout();
189                 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( false ) : null;
190             }
191         };
192     }
193 
194     private Callable<ShutdownResult> createForcedShutdownTask()
195     {
196         return new Callable<ShutdownResult>()
197         {
198             public ShutdownResult call()
199                 throws Exception
200             {
201                 boolean stampedStatusWithTimeout = ParallelComputer.this.forcedShutdownStatus.tryTimeout();
202                 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( true ) : null;
203             }
204         };
205     }
206 
207     private double nanosToSeconds( long nanos )
208     {
209         return (double) nanos / NANOS_IN_A_SECOND;
210     }
211 
212     private boolean hasTimeout()
213     {
214         return timeoutNanos > 0;
215     }
216 
217     private boolean hasTimeoutForced()
218     {
219         return timeoutForcedNanos > 0;
220     }
221 
222     private static long secondsToNanos( double seconds )
223     {
224         double nanos = seconds > 0 ? seconds * NANOS_IN_A_SECOND : 0;
225         return Double.isInfinite( nanos ) || nanos >= Long.MAX_VALUE ? 0 : (long) nanos;
226     }
227 
228     private static long minTimeout( long timeout1, long timeout2 )
229     {
230         if ( timeout1 == 0 )
231         {
232             return timeout2;
233         }
234         else if ( timeout2 == 0 )
235         {
236             return timeout1;
237         }
238         else
239         {
240             return Math.min( timeout1, timeout2 );
241         }
242     }
243 
244     private static void printShutdownHook( Collection<String> executedTests, Collection<String> incompleteTests,
245                                            Future<ShutdownResult> testsBeforeShutdown )
246         throws ExecutionException, InterruptedException
247     {
248         if ( testsBeforeShutdown != null )
249         {
250             for ( final Description test : testsBeforeShutdown.get().getTriggeredTests() )
251             {
252                 if ( test != null && test.getDisplayName() != null )
253                 {
254                     executedTests.add( test.getDisplayName() );
255                 }
256             }
257 
258             for ( final Description test : testsBeforeShutdown.get().getIncompleteTests() )
259             {
260                 if ( test != null && test.getDisplayName() != null )
261                 {
262                     incompleteTests.add( test.getDisplayName() );
263                 }
264             }
265         }
266     }
267 }